
实例化模板时，模板应用模板参数。参数可以使用几种不同的机制来确定:

\begin{itemize}
\item 
显式模板参数:模板名称后面可以跟着用尖括号括起来的显式模板参数。产生的名称称为template-id。

\item 
注入类名:类模板X的作用域内，模板参数P1, P2，…，该模板的名称(X)等价于template-id X<P1, P2, …>。详见13.2.3节。

\item 
默认模板参数:若默认模板参数可用，则可以从模板实例中省略模板参数。但是，对于类模板或别名模板，即使所有模板参数都有默认值，也必须提供(可能为空)尖括号。

\item 
参数类型推导:未显式指定的函数模板参数，可以从调用中的函数调用参数类型推导出来(这在第15章中有详细描述)，其他几种情况下也可以进行推导。若所有模板参数都可以推导，则不需要在函数模板的名称后指定尖括号。C++17还引入了从变量声明或函数表示法，可以从类型转换初始化表达式中推导类模板参数类型;请参阅15.12节。
\end{itemize}

\subsubsubsection{12.3.1\hspace{0.2cm}函数模板参数}

函数模板参数可以显式指定，可以根据模板的使用方式推导出来，也可以作为默认模板参数提供。例如:

\hspace*{\fill} \\ %插入空行
\noindent
\textit{details/max.cpp}
\begin{lstlisting}[style=styleCXX]
template<typename T>
T max (T a, T b)
{
	return b < a ? a : b;
}

int main()
{
	::max<double>(1.0, -3.0); // explicitly specify template argument
	::max(1.0, -3.0); // template argument is implicitly deduced to be double
	::max<int>(1.0, 3.0); // the explicit <int> inhibits the deduction;
	// hence the result has type int
}
\end{lstlisting}

因为对应的模板参数没有出现在函数参数类型中，或者出于其他原因(参见第15.2节)，有些模板参数永远无法推导。相应的参数通常放在模板参数列表的开头，以便在推导其他参数的同时显式地指定。例如:

\hspace*{\fill} \\ %插入空行
\noindent
\textit{details/implicit.cpp}
\begin{lstlisting}[style=styleCXX]
template<typename DstT, typename SrcT>
DstT implicit_cast (SrcT const& x) // SrcT can be deduced, but DstT cannot
{
	return x;
}

int main()
{
	double value = implicit_cast<double>(-1);
}
\end{lstlisting}

若在本例中颠倒了模板参数的顺序(写成了template<typename SrcT, typename DstT>)，则implicit\_cast的调用必须显式指定两个模板参数。

此外，因为没有办法显式地指定或推导，这样的参数不能放在模板参数包之后或出现在偏特化中。

\begin{lstlisting}[style=styleCXX]
template<typename ... Ts, int N>
void f(double (&)[N+1], Ts ... ps); // useless declaration because N
// cannot be specified or deduced
\end{lstlisting}

因为函数模板可以重载，显式地提供函数模板的参数可能不足以标识单个函数:某些情况下，可以标识一组函数:

\begin{lstlisting}[style=styleCXX]
template<typename Func, typename T>
void apply (Func funcPtr, T x)
{
	funcPtr(x);
}

template<typename T> void single(T);

template<typename T> void multi(T);
template<typename T> void multi(T*);

int main()
{
	apply(&single<int>, 3); // OK
	apply(&multi<int>, 7); // ERROR: no single multi<int>
}
\end{lstlisting}

本例中，因为表达式\&single<int>的类型明确，对apply()的第一次调用有效。因此，Func的模板参数值很容易推导。然而，在第二个调用中，\&multi<int>可能是两种不同类型中的一种，这种情况下不能推导Func的模板参数类型。

此外，在函数模板中替换模板参数，可能会导致构造无效的C++类型或表达式。考虑以下重载函数模板(RT1和RT2是未指定类型):

\begin{lstlisting}[style=styleCXX]
template<typename T> RT1 test(typename T::X const*);
template<typename T> RT2 test(...);
\end{lstlisting}

表达式test<int>对于两个函数模板中的第一个没有意义，因为int类型没有成员类型x。然而，第二个模板没有这样的问题。因此，表达式\&test<int>为单个函数的地址。将int替换到第一个模板失败的事实并不意味着表达式无效。SFINAE(替换失败不为过)原则是实现函数模板重载的重要因素，在8.4节和15.7节中进行了讨论。

\subsubsubsection{12.3.2\hspace{0.2cm}类型参数}

模板类型实参为模板类型形参指定的“值”，任何类型(包括void、函数类型、引用类型等)都可以用作模板实参，但对模板形参的替换必须进行有效的构造:

\begin{lstlisting}[style=styleCXX]
template<typename T>
void clear (T p)
{
	*p = 0; // requires that the unary * be applicable to T
}

int main()
{
	int a;
	clear(a); // ERROR: int doesn’t support the unary *
}
\end{lstlisting}

\subsubsubsection{12.3.3\hspace{0.2cm}非类型模板参数}

非类型模板实参是替代非类型形参的值。这样的值必须是以下条件之一:

\begin{itemize}
\item 
具有正确类型的另一个非类型模板的参数。

\item
整型(或枚举)的编译时常量值。只有当相应参数的类型与值的类型匹配，或者值可以隐式转换(不收缩)时，这才可以接受。可以为int形参提供一个char值，但是对于8位char形参，500是一个无效的输入。

\item 
外部变量或函数的名称前面加上内置的一元\&("address of")操作符。对于函数和数组变量，可以省略\&。这样的模板实参匹配指针类型的非类型形参。C++17放宽了这一要求，允许产生指向函数或变量指针的常量表达式。

\item 
对于引用类型的非类型形参来说，前面不带\&操作符的实参是有效的实参。这里，C++17也放宽了限制，允许函数或变量使用任意常量表达式glvalue。

\item 
成员指针常数，形式为\&C::m的表达式，其中C是类类型，m是非静态成员(数据或函数)，这只匹配指针成员类型的非类型形参。C++ 17中，实际语法形式不再受到限制:允许将常量表达式求值匹配为相应的成员常量指针。

\item 
空指针常量，是指针或指针成员类型的非类型形参的有效实参。
\end{itemize}

对于整型非类型形参(可能是最常见的非类型形参类型)，将考虑到形参类型的隐式转换。随着C++11中constexpr的引入，转换前的实参可以具有类类型。

C++17之前，当将实参与指针或引用形参匹配时，用户定义的转换(一个实参的构造函数和转换操作符)和派生是不考虑到转换为基类的。使参数更具有const和/或volatile的隐式转换是可以的。

下面是一些非类型模板参数的示例:

\begin{lstlisting}[style=styleCXX]
template<typename T, T nontypeParam>
class C;

C<int, 33>* c1; // integer type

int a;
C<int*, &a>* c2; // address of an external variable

void f();
void f(int);
C<void (*)(int), f>* c3; // name of a function: overload resolution selects
						// f(int) in this case; the & is implied

template<typename T> void templ_func();
C<void(), &templ_func<double>>* c4; // function template instantiations are functions

struct X {
	static bool b;
	int n;
	onstexpr operator int() const { return 42; }
};

C<bool&, X::b>* c5; // static class members are acceptable variable/function names

C<int X::*, &X::n>* c6; // an example of a pointer-to-member constant

C<long, X{}>* c7; // OK: X is first converted to int via a constexpr conversion
// function and then to long via a standard integer conversion
\end{lstlisting}

模板参数需要在编译器或链接器在构建程序时，能够表示它们的值。在程序运行前不知道的值(例如，局部变量的地址)与构建时实例化模板的概念不兼容。

即便如此，有些常量值目前还是无效的:

\begin{itemize}
\item 
浮点数

\item 
字符串字面值
\end{itemize}

(C++11之前，空指针常量也不允许)

字符串字面值的问题是，两个相同的字面值可以存储在两个不同的地址。常量字符串上实例化模板的另一种(很麻烦)方法，引入一个额外变量来保存字符串:

\begin{lstlisting}[style=styleCXX]
template<char const* str>
class Message {
	...
};

extern char const hello[] = "Hello World!";
char const hello11[] = "Hello World!";

void foo()
{
	static char const hello17[] = "Hello World!";
	
	Message<hello> msg03; // OK in all versions
	Message<hello11> msg11; // OK since C++11
	Message<hello17> msg17; // OK since C++17
}
\end{lstlisting}

要求是声明为引用或指针的非类型模板形参，可以是一个常量表达式，在旧C++版本中具有外部链接，C++11开始的内部链接，或者C++17开始的任意链接方式。

请参阅第17.2节，了解关于该领域未来可能的变化。

以下是其他一些无效的例子:

\begin{lstlisting}[style=styleCXX]
template<typename T, T nontypeParam>
class C;

struct Base {
	int i;
} base;

struct Derived : public Base {
} derived;

C<Base*, &derived>* err1; // ERROR: derived-to-base conversions are not considered

C<int&, base.i>* err2; // ERROR: fields of variables aren’t considered to be variables

int a[10];
C<int*, &a[0]>* err3; // ERROR: addresses of array elements aren’t acceptable either
\end{lstlisting}


\subsubsubsection{12.3.4\hspace{0.2cm}双重模板参数}

双重模板实参通常必须是类模板或别名模板，其形参必须与它所替换的双重模板参数的形参精确匹配。C++17前，双重模板参数的默认模板实参会忽略(但如果双重模板参数有默认实参，会在模板实例化期间考虑)。C++17放宽了匹配规则，只要求模板形参至少与对应的模板形参一样特化(参见16.2.2节)。

这使得以下示例在C++17前无效:

\begin{lstlisting}[style=styleCXX]
#include <list>
	// declares in namespace std:
	// template<typename T, typename Allocator = allocator<T>>
	// class list;

template<typename T1, typename T2,
		template<typename> class Cont> // Cont expects one parameter
class Rel {
	...
};

Rel<int, double, std::list> rel; // ERROR before C++17: std::list has more than
								// one template parameter
\end{lstlisting}

本例中的问题是标准库的std::list模板有多个参数。第二个参数(用于描述分配器)有默认值，但在C++17前，将std::list匹配到Container参数时，不会考虑默认值。

可变参数模板参数是上述C++17前“精确匹配”规则的例外，其为这个限制提供了一个解决方案:支持对双重模板参数进行更通用的匹配。一个双重模板参数包可以匹配零个或多个在双重模板参数中同类的模板形参:

\begin{lstlisting}[style=styleCXX]
#include <list>

template<typename T1, typename T2,
		template<typename... > class Cont> // Cont expects any number of
class Rel { // type parameters
	...
};

Rel<int, double, std::list> rel; // OK: std::list has two template parameters
								// but can be used with one argument
\end{lstlisting}

模板参数包只能匹配同类的模板实参。下面的类模板可以用只有模板类型参数的类模板或别名模板实例化，因为作为TT传递的模板类型参数包可以匹配零个或多个模板类型参数:

\begin{lstlisting}[style=styleCXX]
#include <list>
#include <map>
	// declares in namespace std:
	// template<typename Key, typename T,
	// typename Compare = less<Key>,
	// typename Allocator = allocator<pair<Key const, T>>>
	// class map;
#include <array>
	// declares in namespace std:
	// template<typename T, size_t N>
	// class array;
	
template<template<typename... > class TT>
class AlmostAnyTmpl {
};

AlmostAnyTmpl<std::vector> withVector; // two type parameters
AlmostAnyTmpl<std::map> withMap; // four type parameters
AlmostAnyTmpl<std::array> withArray; // ERROR: a template type parameter pack
									// doesn’t match a nontype template parameter
\end{lstlisting}

C++17前，只有关键字class可以用来声明双重模板参数，这并不意味着只有用关键字class声明的类模板才允许作为替换参数。实际上，struct模板、union模板和alias模板都是双重模板参数的有效参数(C++11引入别名模板后)。这类似于任何类型都可以使用关键字class声明的模板类型参数。

\subsubsubsection{12.3.5\hspace{0.2cm}等价参数}

当参数的值——相同时，两组模板实参是等价的。对于类型参数，类型别名并不重要:要比较的是类型别名声明的最终类型。对于整型非类型实参，比较实参的值;如何表达这个值并不重要:

\begin{lstlisting}[style=styleCXX]
template<typename T, int I>
class Mix;

using Int = int;

Mix<int, 3*3>* p1;
Mix<Int, 4+5>* p2; // p2 has the same type as p1
\end{lstlisting}

(从这个例子可以清楚地看出，建立模板参数列表的等价性不需要定义模板)

然而上下文中，模板参数的“值”不能确定，因此等价规则会变得稍微复杂一些。考虑下面的例子:

\begin{lstlisting}[style=styleCXX]
template<int N> struct I {};

template<int M, int N> void f(I<M+N>); // #1
template<int N, int M> void f(I<N+M>); // #2

template<int M, int N> void f(I<N+M>); // #3 ERROR
\end{lstlisting}

仔细研究\#1和\#2声明，通过将M和N分别重命名为N和M，会得到相同的声明:因此，这两个表达式等价，声明了相同的函数模板f。这两个声明中的表达式M+N和N+M等价。

然而，声明\#3略有不同:操作数的顺序反了。这使得\#3中的表达式N+M不等于其他两个表达式。由于表达式将为所涉及的模板参数的值产生相同的结果，这些表达式功能等效。如果模板声明的方式不同，只是因为声明包含了不相等的功能等效表达式，那就是错误的。这样的错误不需要编译器诊断，因为有些编译器可能在内部以与N+2完全相同的方式表示N+1+1，而其他编译器可能不会这样。标准方面并没有强制一种特定的实现方法，而是允许其中任何一种方法，所以开发者在这方面需要三思而后行。

从函数模板生成的函数永远不等同于普通函数，即使具有相同的类型和相同的名称。这对类成员有两个重要的影响:

\begin{enumerate}
\item
从成员函数模板生成的函数不重写虚函数。

\item
从构造函数模板生成的构造函数不是复制或移动构造函数。

\begin{tcolorbox}[colback=webgreen!5!white,colframe=webgreen!75!black]
\hspace*{0.75cm}默认构造函数可以构造函数模板。
\end{tcolorbox}

类似地，从赋值模板生成的赋值操作不是复制赋值操作符或移动赋值操作符。(然而，因为隐式调用复制赋值或移动赋值操作符不太常见，所以这不太容易出现问题。)
\end{enumerate}

这种方式优缺点并存。请参阅6.2节和6.4节了解详细信息。











